//-----------------------------------------------------------------------------
// Bend2D - Physics Engine
// Roberto Bender <beto.bender@gmail.com>
// Description: 
// Date: 15/03/2010
//-----------------------------------------------------------------------------
#include "world.h"
#include "durable.h"
#include "factory.h"
#include "bexception.h"
#include "log.h"
using namespace bend2d;

#include <iostream>
using namespace std;

World::World() :
    m_WorldBoundary(0, 0, 100, 100),
    m_TimeSinceLastFrame(0)
{
#ifdef BEND2DLOG
    log_info("World Created");
#endif
    
    m_RealBoundary[leftBound] = Factory::GetInstance()->AccessObjectCreator()->CreateWorldBoundary();
    m_RealBoundary[topBound] = Factory::GetInstance()->AccessObjectCreator()->CreateWorldBoundary();
    m_RealBoundary[rightBound] = Factory::GetInstance()->AccessObjectCreator()->CreateWorldBoundary();
    m_RealBoundary[bottomBound] = Factory::GetInstance()->AccessObjectCreator()->CreateWorldBoundary();

    RegistryObject(m_RealBoundary[leftBound]);
    RegistryObject(m_RealBoundary[topBound]);
    RegistryObject(m_RealBoundary[rightBound]);
    RegistryObject(m_RealBoundary[bottomBound]);

    RecreateRealBoundary();

    CollideRegions::SetWorld(this);

    m_IterationStopWatch.Start();
}

World::World(const bend2d::Rectangle& boundary) :
    m_WorldBoundary(boundary),
    m_TimeSinceLastFrame(0)
{
#ifdef BEND2DLOG
    log_info("World Created");
#endif
    
    m_RealBoundary[leftBound] = Factory::GetInstance()->AccessObjectCreator()->CreateWorldBoundary();
    m_RealBoundary[topBound] = Factory::GetInstance()->AccessObjectCreator()->CreateWorldBoundary();
    m_RealBoundary[rightBound] = Factory::GetInstance()->AccessObjectCreator()->CreateWorldBoundary();
    m_RealBoundary[bottomBound] = Factory::GetInstance()->AccessObjectCreator()->CreateWorldBoundary();

    RegistryObject(m_RealBoundary[leftBound]);
    RegistryObject(m_RealBoundary[topBound]);
    RegistryObject(m_RealBoundary[rightBound]);
    RegistryObject(m_RealBoundary[bottomBound]);

    RecreateRealBoundary();

    CollideRegions::SetWorld(this);

    m_IterationStopWatch.Start();
}

void World::RecreateRealBoundary()
{
    m_RealBoundary[leftBound]->SetLine(m_WorldBoundary.GetLeftLine());
    m_RealBoundary[topBound]->SetLine(m_WorldBoundary.GetTopLine());
    m_RealBoundary[rightBound]->SetLine(m_WorldBoundary.GetRightLine());
    m_RealBoundary[bottomBound]->SetLine(m_WorldBoundary.GetBottomLine());
}

const bend2d::Rectangle& World::GetBoundary() const
{
    return m_WorldBoundary;
}

void World::SetBoundary(const bend2d::Rectangle& boundary)
{
    m_WorldBoundary = boundary;
    RecreateRealBoundary();
}

void World::RegistryObject(list<FastCast*> objects)
{
    for(list<FastCast*>::iterator it = objects.begin(); it != objects.end(); ++it)
        RegistryObject(*it);
}

void World::RegistryObject(FastCast* object)
{
    if(NULL == object)
        throw RuntimeException("Null FastCast Object Added");
        
    object->SetWorld(this);
     
    m_FastCastList.push_front(object);
    object->GetFastCastIterator() = m_FastCastList.begin();

    if(object->IsDrawable())
    {
        Drawable* drawableObject = dynamic_cast<Drawable*>(object);
        m_DrawableList.push_front(drawableObject);
        drawableObject->GetDrawableIterator() = m_DrawableList.begin();
    }

    if(object->IsMoveable())
    {
        Moveable* moveableObject = dynamic_cast<Moveable*>(object);
        m_MoveableList.push_front(moveableObject);
        moveableObject->GetMoveableIterator() = m_MoveableList.begin();
    }

    if(object->IsDurable())
    {
        Durable* durableObject = dynamic_cast<Durable*>(object);
        m_DurableList.push_front(durableObject);
        durableObject->GetDurableIterator() = m_DurableList.begin();
    }

    if(object->IsCollidable())
    {
        Collidable* collidableObject = dynamic_cast<Collidable*>(object);
        m_CollidableList.push_front(collidableObject);
        collidableObject->GetCollidableIterator() = m_CollidableList.begin();
    }

    if(object->IsPhysicable())
    {
        Physicable* physicableObject = dynamic_cast<Physicable*>(object);
        m_PhysicableList.push_front(physicableObject);
        physicableObject->GetPhysicableIterator() = m_PhysicableList.begin();
    }
}

void World::UnRegistryObject(FastCast* object, bool destroy)
{
    if(object->IsDrawable())
        m_DrawableList.erase(dynamic_cast<Drawable*>(object)->GetDrawableIterator());

    if(object->IsMoveable())
        m_MoveableList.erase(dynamic_cast<Moveable*>(object)->GetMoveableIterator());
    
    if(object->IsDurable())
        m_DurableList.erase(dynamic_cast<Durable*>(object)->GetDurableIterator());
        
    if(object->IsCollidable())
        m_CollidableList.erase(dynamic_cast<Collidable*>(object)->GetCollidableIterator());
     
    if(object->IsPhysicable())
        m_PhysicableList.erase(dynamic_cast<Physicable*>(object)->GetPhysicableIterator());
    
    m_FastCastList.erase(object->GetFastCastIterator());
     
    if(destroy)
        delete object;
}

void World::Iterate(double timeSinceLastFrame)
{
    if(timeSinceLastFrame == 0)
        return;

    log_info("Iterate Call start, timeSinceLastFrame = " << timeSinceLastFrame);

    m_TimeSinceLastFrame = timeSinceLastFrame;
    m_IterationStopWatch.Reset();

    // Apply Physics Forces //
    for(Physicable::PhysicableList::iterator it = m_PhysicableList.begin(); it != m_PhysicableList.end(); ++it)
    {
        (*it)->ResetForces();
        for(PhysicForce::PhysicForceList::iterator innerit = m_PhysicForceList.begin(); innerit != m_PhysicForceList.end(); ++innerit)
            (*innerit)->ApplyPhysics(*it);
    }

    // Create the Movement //
    for(Moveable::MoveableList::iterator it = m_MoveableList.begin(); it != m_MoveableList.end(); ++it)
    {
        (*it)->ResetMove();
        FastCast* cast = dynamic_cast<FastCast*>((*it));
        if(cast && cast->IsPhysicable())
            (*it)->SetExternalAcceleration(dynamic_cast<Physicable*>(cast)->GetResultForces());
        (*it)->Move(timeSinceLastFrame);
    }

    // Check Collisions //
    list<Collidable*> regionsWillCollide;
    RecreateRegions(regionsWillCollide);
    while(regionsWillCollide.size() > 0)
    {
        regionsWillCollide.clear();
        RecreateRegions(regionsWillCollide);
    }

    // Using Resetter
    for(FastCast::FastCastList::iterator it = m_FastCastList.begin(); it != m_FastCastList.end(); ++it)
    {
        if((*it)->IsDurable())
        {
            Durable* durCast = dynamic_cast<Durable*>(*it);
            if(!durCast->Live(timeSinceLastFrame))
            {
                if(NULL != durCast->GetDurableResetter())
                {
                    durCast->GetDurableResetter()->Reset(*it);
                }
                durCast->Reset();
            }
        }
    }

    if(!m_Flags[eTwoStepsIteration])
    {
        // Finish the Movement //
        for(Moveable::MoveableList::iterator it = m_MoveableList.begin(); it != m_MoveableList.end(); ++it)
            (*it)->Moved();
    }

    log_info("Iterate Call finish");
}

void World::Iterated()
{
    log_info("Iterated call start");

    // Finish the Movement //
    for(Moveable::MoveableList::iterator it = m_MoveableList.begin(); it != m_MoveableList.end(); ++it)
        (*it)->Moved();
    
    log_info("Iterated call finish");
}

void World::Draw() 
{
    log_info("Draw call start");
    for(Drawable::DrawableList::reverse_iterator it = m_DrawableList.rbegin(); it != m_DrawableList.rend(); ++it)
        if((*it)->GetIsVisible())
            (*it)->Draw();
    log_info("Draw call finish");
}

void World::RegistryPhysicForce(PhysicForce* force)
{
    m_PhysicForceList.push_back(force);
}

void World::Enable(eWorldFlags flag)
{
    m_Flags[flag] = true;
}

void World::Disable(eWorldFlags flag)
{
    m_Flags[flag] = false;
}

bool World::GetFlag(eWorldFlags flag)
{
    return m_Flags[flag];
}

double World::GetTimeSinceLastIteration() const
{
#if BEND2DLOG
    log_info("Returning: " << m_IterationStopWatch.GetTime() / 1000.0);
#endif
    return (double) m_IterationStopWatch.GetTime() / 1000.0;
}

#ifdef BEND2D_DEBUG
void World::DrawNew()
{
    for(Drawable::DrawableList::reverse_iterator it = m_DrawableList.rbegin(); it != m_DrawableList.rend(); ++it)
        (*it)->DrawNew();
}
#endif
